#ifndef L2_DATASET_HPP
#define L2_DATASET_HPP

#include <cstddef>
#include <cstdio>

class DataSet {
public:
    class MemAllocator {
    public:
        virtual void *malloc(size_t size) = 0;
        virtual void free(void *mem) = 0;
    };

    enum class DataType {
        Invaild,
        Boolean,
        Number,
        String,
    };

    enum ErrorNumber {
        Error_None = 0,
        Error_Error = 1,
        Error_IllegalIndex = 2,
        Error_DataTypeError = 3,
        Error_NullLabel = 4,
        Error_NullString = 5,
        Error_AddDataWithExistLabel = 6, 
        Error_LabelNotExist = 7,
        Error_MallocFailed = 8,
        Error_FreeFailed = 9,
        Error_DataSetError = 10,
    };

    struct Data {
        DataType data_type;
        char *label;
        int label_size;
        void *value;
        int value_size;
    };
private:
    template <typename T>
    class List {
    public:
        enum ErrorNumber {
            Error_None,
            Error_MallocFailed,
            Error_FreeFailed,
            Error_IllegalIndex,
        };
    private:
        struct Item {
            T value;
            Item *next;
        };
    public:
        List(){
            this->header = nullptr;
            this->size = 0;
            this->error_number = Error_None;
        }

        ~List(){
            this->clear();
        }

        bool append(T value){
            Item **curr = &this->header;
            while ( *curr != nullptr ){
                curr = &((*curr)->next);
            }
            
            if ( this->mem_allocator != nullptr ){
                (*curr) = (Item*)this->mem_allocator->malloc(sizeof(Item));
            }else{
                (*curr) = new Item;
            }
        
            if ( *curr == nullptr ){
                this->error_number = Error_MallocFailed;
                return false;
            }
            (*curr)->value = value;
            (*curr)->next = nullptr;
            this->size += 1;
            this->error_number = Error_None;
            return true;
        }

        bool insert(T value,int pos){
            if ( pos > this->size ){
                this->error_number = Error_IllegalIndex;
                return false;
            }
            Item **curr = &this->header;
            int curr_index = 0;
            while ( *curr != nullptr && curr_index < pos ){
                curr = &((*curr)->next);
                curr_index += 1;
            }
            Item *new_item = nullptr;
            
            if ( this->mem_allocator != nullptr ){
                new_item = (Item*)this->mem_allocator->malloc(sizeof(Item));
            }else{
                new_item = new Item;
            }
            
            if ( new_item == nullptr ){
                this->error_number = Error_MallocFailed;
                return false;
            }
            new_item->value = value;
            new_item->next = (*curr);
            (*curr) = new_item;
            this->size += 1;
            this->error_number = Error_None;
            return true;
        }

        bool remove(int pos){
            int curr_pos = -1;
            Item **curr = &this->header;
            Item **prev = &this->header;
            while ( curr_pos != pos && *curr != nullptr ){
                prev = curr;
                curr = &((*curr)->next);
                curr_pos += 1;
            }
            if ( *prev == nullptr ){
                this->error_number = Error_IllegalIndex;
                return false;
            }
            Item *temp = (*curr);

            if ( this->mem_allocator != nullptr ){
                this->mem_allocator->free((*prev));
            }else{
                delete (*prev);
            }
            
            (*prev) = temp;
            this->size -= 1;
            this->error_number = Error_None;
            return true;
        }

        bool set(T value,int pos){
            int curr_pos = 0;
            Item **curr = &this->header;
            while ( curr_pos != pos && *curr != nullptr ){
                curr = &((*curr)->next);
                curr_pos += 1;
            }
            if ( *curr == nullptr ){
                this->error_number = Error_IllegalIndex;
                return false;
            }
            (*curr)->value = value;
            this->error_number = Error_None;
            return true;
        }

        T get(int pos){
            T ret;
            int curr_pos = 0;
            Item **curr = &this->header;
            for ( unsigned int nbyte = 0; nbyte < sizeof(T); nbyte++ ){
                ((unsigned char*)&ret)[nbyte] = 0;
            }
            while ( curr_pos != pos && *curr != nullptr ){
                curr = &((*curr)->next);
                curr_pos += 1;
            }
            if ( *curr == nullptr ){
                this->error_number = Error_IllegalIndex;
                return ret;
            }
            this->error_number = Error_None;
            return (*curr)->value;
        }

        bool clear(void){
            while ( this->getSize() > 0 ){
                if ( !this->remove(0) ){
                    return false;
                }
            }
            return true;
        }

        int getSize(void){
            return this->size;
        }

        ErrorNumber getErrorNumber(void){
            return this->error_number;
        }

        void setMemAllocator(MemAllocator *mem_allocator){
            this->mem_allocator = mem_allocator;
        }

    private:
        MemAllocator *mem_allocator = nullptr;
        Item *header;
        int size;
        ErrorNumber error_number;
    };

public:
    explicit DataSet();
    virtual ~DataSet();
    virtual bool add(const char *label,bool value);
    virtual bool add(const char *label,double value);
    virtual bool add(const char *label,const char *value);
    virtual bool remove(const char *label);
    virtual bool set(const char *label,bool value);
    virtual bool set(const char *label,double value);
    virtual bool set(const char *label,const char *value);
    virtual bool getBoolean(const char *label);
    virtual double getNumber(const char *label);
    virtual const char *getString(const char *label);
    virtual bool hasLabel(const char *label);
    virtual DataType getDataType(const char *label);

    virtual bool clear(void);
    virtual int getDataCount(void);
    virtual struct Data *getDataItem(int pos);

    virtual int getErrorNumber(void);

    virtual void setMemAllocator(MemAllocator *mem_allocator);
protected:
    MemAllocator *mem_allocator = nullptr;
    List<Data*> dataset;
    int error_number;
};

#endif